// Copyright 2019 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package eventlog parses kernel event logs and saves the parsed data on a file on disk.
package eventlog

import (
	"fmt"
	"io"
	"log"
	"os"
	"strings"

	slaunch "github.com/u-root/u-root/pkg/securelaunch"
	tss "github.com/u-root/u-root/pkg/tss"
	txtlog "github.com/u-root/u-root/pkg/txtlog"
)

// EventLog stores location for dumping event logs on disk.
type EventLog struct {
	Type     string `json:"type"`
	Location string `json:"location"`
}

const (
	eventLogFile        = "/sys/kernel/security/slaunch/eventlog"
	defaultEventLogFile = "eventlog.txt" // only used if user doesn't provide any
)

// Add writes event logs to sysfs file.
func Add(b []byte) error {
	fd, err := os.OpenFile(eventLogFile, os.O_WRONLY, 0o644)
	if err != nil {
		return err
	}

	defer fd.Close()
	_, err = fd.Write(b)
	if err != nil && err != io.EOF {
		return err
	}
	slaunch.Debug("err = %v", err)
	return nil
}

// parseEvtlog uses the tpmtool package to parse the event logs generated by a
// kernel with CONFIG_SECURE_LAUNCH enabled and returns the parsed data in a
// byte slice.
//
// These event logs are originally in binary format and need to be parsed into
// human readable format. An error is returned if parsing code fails in tpmtool.
func parseEvtLog(evtLogFile string) ([]byte, error) {
	txtlog.DefaultTCPABinaryLog = evtLogFile
	firmware := txtlog.Txt
	TPMSpecVersion := tss.TPMVersion20
	tcpaLog, err := txtlog.ParseLog(firmware, TPMSpecVersion)
	if err != nil {
		return nil, err
	}

	var w strings.Builder
	for _, pcr := range tcpaLog.PcrList {
		fmt.Fprintf(&w, "%s\n", pcr)
		fmt.Fprintf(&w, "\n")
	}
	return []byte(w.String()), nil
}

// Parse uses tpmtool to parse event logs generated by the kernel into human
// readable format, and queues the data to persist queue.
//
// The location of the file on disk is specified in policy file by Location tag.
// Returns an error if parsing the event log fails or user enters an incorrect
// format for input.
func (e *EventLog) Parse() error {
	if e.Type != "file" {
		return fmt.Errorf("unsupported eventlog type exiting")
	}

	slaunch.Debug("Identified EventLog Type = file")

	// e.Location is of the form sda:path/to/file.txt
	eventlogPath := e.Location
	if eventlogPath == "" {
		return fmt.Errorf("empty eventlog path provided exiting")
	}

	// parse eventlog
	data, err := parseEvtLog(eventLogFile)
	if err != nil {
		log.Printf("tpmtool could NOT parse Eventlogfile=%s, err=%s", eventLogFile, err)
		return fmt.Errorf("parseEvtLog err=%v", err)
	}

	return slaunch.AddToPersistQueue("EventLog:", data, eventlogPath, defaultEventLogFile)
}
